home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / pluginy Firefox / 7684 / 7684.xpi / resources / fmSecret.js < prev    next >
Text File  |  2009-11-20  |  14KB  |  458 lines

  1. /**
  2.  * Copyright (c) 2008, Jose Enrique Bolanos, Jorge Villalobos
  3.  * All rights reserved.
  4.  *
  5.  * THE CONTENTS OF THIS FILE ARE *NOT* OPEN SOURCE, AS IT CONTAINS CONFIDENTIAL
  6.  * INFORMATION THAT SHOULD NOT BE USED BY OTHER APPLICATIONS, EVEN IF THEY
  7.  * INTEND TO CONNECT TO THE LAST.FM API.
  8.  *
  9.  * Read http://www.last.fm/api/webauth for more information on secret/public
  10.  * keys.
  11.  **/
  12.  
  13. var EXPORTED_SYMBOLS = [];
  14.  
  15. const Cc = Components.classes;
  16. const Ci = Components.interfaces;
  17.  
  18. Components.utils.import("resource://firefm/fmCommon.js");
  19.  
  20. // Regular expression used for unescaping control characters.
  21. const RE_UNESCAPE_REPLACE = /!\d\d?\d?!/g;
  22. // Pre-computed multiplicative inverse in GF(2^8).
  23. const SBOX =
  24.   [ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b,
  25.     0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
  26.     0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26,
  27.     0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
  28.     0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2,
  29.     0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
  30.     0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed,
  31.     0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
  32.     0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f,
  33.     0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
  34.     0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec,
  35.     0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
  36.     0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14,
  37.     0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
  38.     0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d,
  39.     0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
  40.     0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f,
  41.     0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
  42.     0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11,
  43.     0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
  44.     0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f,
  45.     0xb0, 0x54, 0xbb, 0x16 ];
  46. // Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)].
  47. const RCON =
  48.   [ [ 0x00, 0x00, 0x00, 0x00 ], [ 0x01, 0x00, 0x00, 0x00 ],
  49.     [ 0x02, 0x00, 0x00, 0x00 ], [ 0x04, 0x00, 0x00, 0x00 ],
  50.     [ 0x08, 0x00, 0x00, 0x00 ], [ 0x10, 0x00, 0x00, 0x00 ],
  51.     [ 0x20, 0x00, 0x00, 0x00 ], [ 0x40, 0x00, 0x00, 0x00 ],
  52.     [ 0x80, 0x00, 0x00, 0x00 ], [ 0x1b, 0x00, 0x00, 0x00 ],
  53.     [ 0x36, 0x00, 0x00, 0x00 ] ];
  54. // Hexadecimal character mapping.
  55. const HEX_CHARS = "0123456789abcdef";
  56. // MD5 bits per input character. 8 - ASCII; 16 - Unicode.
  57. const MD5_BITS_PER_CHAR = 8;
  58.  
  59. const ENC = "ck6krit14yAhMTYwIUsFSoYv7aHtRPD7TCuVwUKcPLiiFbNuSwRTyiEwIV4=";
  60.  
  61. var appKey = null;
  62.  
  63. /**
  64.  * Handles Fire.fm's secret information, specifically it's application secret
  65.  * key, used to sign information sent to the Last.fm API.
  66.  */
  67. FireFM.Secret = {
  68.   /* Logger for this object. */
  69.   _logger : null,
  70.  
  71.   /**
  72.    * Initializes this object.
  73.    */
  74.   init : function() {
  75.     let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  76.     let that = this;
  77.  
  78.     this._logger = FireFM.getLogger("FireFM.Secret");
  79.     this._logger.debug("init");
  80.   },
  81.  
  82.   /* The not-so-secret API key. */
  83.   get API_KEY() { return "96565ee5297d63435d890a7f8320e890"; },
  84.  
  85.   /**
  86.    * Generates an authentication string used to create a Scrobble session.
  87.    * @param aTimestamp timestamp used to generate the string.
  88.    * @return the authentication string for the Scrobble handshake.
  89.    */
  90.   generateScrobbleAuth : function(aTimestamp) {
  91.     this._logger.debug("generateScrobbleAuth");
  92.  
  93.     if (null == appKey) {
  94.       appKey = this._decryptAES128(ENC, FireFM.EXTENSION_UUID);
  95.     }
  96.  
  97.     return this._md5Hash(appKey + aTimestamp);
  98.   },
  99.  
  100.   /**
  101.    * Generates a signature for the parameter string.
  102.    * @param aParams the parameter string to be sent to the API.
  103.    * @return the signature that serves to authenticate this application and the
  104.    * parameters being sent to the API.
  105.    */
  106.   generateSignature : function(aParams) {
  107.     this._logger.debug("generateSignature");
  108.  
  109.     if (null == appKey) {
  110.       appKey = this._decryptAES128(ENC, FireFM.EXTENSION_UUID);
  111.     }
  112.  
  113.     return this._md5Hash(aParams + appKey);
  114.   },
  115.  
  116.   /**
  117.    * Decrypts a string of data using the 128 bit AES encryption algorithm.
  118.    * Note: This method is based on code inside Fire Encrypter:
  119.    * https://addons.mozilla.org/en-US/firefox/addon/3208
  120.    * @author Ronald van den Heetkamp.
  121.    * @author Jorge Villalobos (minor modifications)
  122.    * @param aEncryptedData the data to decrypt.
  123.    * @param aKey the key used to decrypt the data.
  124.    * @return the decrypted data with the 128 bit AES algorithm, using the given
  125.    * key.
  126.    * @throws Exception if any of the arguments is invalid.
  127.    */
  128.   _decryptAES128 : function(aEncryptedData, aKey) {
  129.     this._logger.trace("_decryptAES128");
  130.  
  131.     let hiddenWindow =
  132.       Cc["@mozilla.org/appshell/appShellService;1"].
  133.         getService(Ci.nsIAppShellService).hiddenDOMWindow;
  134.     let pwBytes = new Array(16);
  135.     let counterBlock = new Array(16);
  136.     let pwKeySchedule =
  137.       this._keyExpansion([ 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1 ]);
  138.     let key;
  139.     let keySchedule;
  140.     let ctrTxt;
  141.     let plaintext;
  142.     let cipherCntr;
  143.     let pt;
  144.     let encryptedDataByte;
  145.     let plaintextByte;
  146.  
  147.     for (let i = 0; i < 16; i++) {
  148.       pwBytes[i] = aKey.charCodeAt(i);
  149.     }
  150.  
  151.     key = this._cipher(pwBytes, pwBytes, pwKeySchedule);
  152.     keySchedule = this._keyExpansion(key);
  153.     // split aEncryptedData into array of block-length strings.
  154.     aEncryptedData = hiddenWindow.atob(aEncryptedData).split('+');
  155.     // recover nonce from 1st element of aEncryptedData.
  156.     ctrTxt = this._unescCtrlChars(aEncryptedData[0]);
  157.  
  158.     for (let i = 0; i < 8; i++) {
  159.       counterBlock[i] = ctrTxt.charCodeAt(i % 4);
  160.     }
  161.  
  162.     plaintext = new Array(aEncryptedData.length - 1);
  163.  
  164.     for (let b = 1; b < aEncryptedData.length; b++) {
  165.       for (let c = 0; c < 8; c++) {
  166.         // set counter in counter block.
  167.         counterBlock[15 - c] = ((b - 1) >>> c * 8) & 0xff;
  168.       }
  169.       // encrypt counter block.
  170.       cipherCntr = this._cipher(counterBlock, key, keySchedule);
  171.       aEncryptedData[b] = this._unescCtrlChars(aEncryptedData[b]);
  172.       pt = '';
  173.  
  174.       for (let i = 0; i < aEncryptedData[b].length; i++) {
  175.         encryptedDataByte = aEncryptedData[b].charCodeAt(i);
  176.         plaintextByte = encryptedDataByte ^ cipherCntr[i];
  177.         pt += String.fromCharCode(plaintextByte);
  178.       }
  179.  
  180.       plaintext[b] = pt;
  181.     }
  182.  
  183.     return unescape(plaintext.join(''));
  184.   },
  185.  
  186.   /**
  187.    * Apply the AES cypher.
  188.    * @param aInput the input to cypher.
  189.    * @param aKey the key used to encrypt / decrypt.
  190.    * @param aKeySchedule the key shedule to use for the cypher.
  191.    * @return input string with the cypher applied to it.
  192.    */
  193.   _cipher : function(aInput, aKey, aKeySchedule) {
  194.     this._logger.trace("_cipher");
  195.  
  196.     let Nk = aKey.length / 4; // key length (in words).
  197.     let Nr = Nk + 6; // no of rounds.
  198.     let Nb = 4; // block size: no of columns in state (fixed at 4 for AES).
  199.     let state = [[],[],[],[]];
  200.     let output;
  201.  
  202.     // initialise 4xNb byte-array 'state' with input.
  203.     for (let i = 0; i < 4 * Nb; i++) {
  204.       state[i % 4][Math.floor(i / 4)] = aInput[i];
  205.     }
  206.  
  207.     state = this._addRoundKey(state, aKeySchedule, 0, Nb);
  208.  
  209.     for (let round = 1; round < Nr; round++) {
  210.       state = this._subBytes(state, Nb);
  211.       state = this._shiftRows(state, Nb);
  212.       state = this._mixColumns(state, Nb);
  213.       state = this._addRoundKey(state, aKeySchedule, round, Nb);
  214.     }
  215.  
  216.     state = this._subBytes(state, Nb);
  217.     state = this._shiftRows(state, Nb);
  218.     state = this._addRoundKey(state, aKeySchedule, Nr, Nb);
  219.     output = new Array(4 * Nb); // convert to 1-d array before returning
  220.  
  221.     for (let i = 0; i< 4 * Nb; i++) {
  222.       output[i] = state[i % 4][Math.floor(i / 4)];
  223.     }
  224.  
  225.     return output;
  226.   },
  227.  
  228.   /**
  229.    * Apply sbox to state S [┬º5.1.1].
  230.    * @param aState the state to transform.
  231.    * @param aColumnCount the number of columns in the state.
  232.    * @return the transformed state.
  233.    */
  234.   _subBytes : function(aState, aColumnCount) {
  235.     this._logger.trace("_subBytes");
  236.  
  237.     for (let r = 0; r < 4; r++) {
  238.       for (let c = 0; c < aColumnCount; c++) {
  239.         aState[r][c] = SBOX[aState[r][c]];
  240.       }
  241.     }
  242.  
  243.     return aState;
  244.   },
  245.  
  246.   /**
  247.    * Shift row r of state S left by r bytes [┬º5.1.2].
  248.    * @param aState the state to transform.
  249.    * @param aColumnCount the number of columns in the state.
  250.    * @return the transformed state.
  251.    */
  252.   _shiftRows : function(aState, aColumnCount) {
  253.     this._logger.trace("_shiftRows");
  254.  
  255.     let t = new Array(4);
  256.     // note that this will work for Nb=4,5,6, but not 7,8: see
  257.     // fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf
  258.     for (let r = 1; r < 4; r++) {
  259.       for (let c = 0; c < 4; c++) {
  260.         t[c] = aState[r][(c + r) % aColumnCount]; // shift into temp copy.
  261.       }
  262.  
  263.       for (let c = 0; c < 4; c++) {
  264.         aState[r][c] = t[c]; // and copy back.
  265.       }
  266.     }
  267.  
  268.     return aState;
  269.   },
  270.  
  271.   /**
  272.    * Combine bytes of each col of state S [┬º5.1.3].
  273.    * @param aState the state to transform.
  274.    * @param aColumnCount the number of columns in the state.
  275.    * @return the transformed state.
  276.    */
  277.   _mixColumns : function(aState, aColumnCount) {
  278.     this._logger.trace("_mixColumns");
  279.  
  280.     let a;
  281.     let b;
  282.  
  283.     for (let c = 0; c < 4; c++) {
  284.       a = new Array(4);  // 'a' is a copy of the current column from 's'.
  285.       b = new Array(4);  // 'b' is a┬ò{02} in GF(2^8).
  286.  
  287.       for (let i = 0; i < 4; i++) {
  288.         a[i] = aState[i][c];
  289.         b[i] =
  290.           ((aState[i][c] & 0x80) ? (aState[i][c] << 1 ^ 0x011b) :
  291.            (aState[i][c] << 1));
  292.       }
  293.  
  294.       // a[n] ^ b[n] is a┬ò{03} in GF(2^8).
  295.       aState[0][c] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]; // 2*a0 + 3*a1 + a2 + a3
  296.       aState[1][c] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; // a0 * 2*a1 + 3*a2 + a3
  297.       aState[2][c] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; // a0 + a1 + 2*a2 + 3*a3
  298.       aState[3][c] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3]; // 3*a0 + a1 + a2 + 2*a3
  299.     }
  300.  
  301.     return aState;
  302.   },
  303.  
  304.   /**
  305.    * xor Round Key into state S [┬º5.1.4].
  306.    * @param aState the state to transform.
  307.    * @param aKeySchedule the key shedule to use for the cypher.
  308.    * @param aRound the round number.
  309.    * @param aColumnCount the number of columns in the state.
  310.    * @return the transformed state.
  311.    */
  312.   _addRoundKey : function(aState, aKeySchedule, aRound, aColumnCount) {
  313.     this._logger.trace("_addRoundKey");
  314.  
  315.     for (let r = 0; r < 4; r++) {
  316.       for (let c = 0; c < aColumnCount; c++) {
  317.         aState[r][c] ^= aKeySchedule[aRound * 4 + c][r];
  318.       }
  319.     }
  320.  
  321.     return aState;
  322.   },
  323.  
  324.   /**
  325.    * Generate Key Schedule (byte-array Nr+1 x Nb) from Key [┬º5.2].
  326.    * @param aKey the key used to generate the schedule.
  327.    * @return the key schedule generated with the key.
  328.    */
  329.   _keyExpansion : function(aKey) {
  330.     this._logger.trace("_keyExpansion");
  331.  
  332.     let Nk = aKey.length / 4; // key length (in words).
  333.     let Nr = Nk + 6; // no of rounds.
  334.     let Nb = 4; // block size: no of columns in state (fixed at 4 for AES).
  335.     let w = new Array(Nb * (Nr + 1));
  336.     let temp = new Array(4);
  337.     let r;
  338.  
  339.     for (let i = 0; i < Nk; i++) {
  340.       r = [aKey[4 * i], aKey[4 * i + 1], aKey[4 * i + 2], aKey[4 * i + 3]];
  341.       w[i] = r;
  342.     }
  343.  
  344.     for (let i = Nk; i < (Nb * (Nr + 1)); i++) {
  345.       w[i] = new Array(4);
  346.  
  347.       for (let t = 0; t < 4; t++) {
  348.         temp[t] = w[i - 1][t];
  349.       }
  350.  
  351.       if (i % Nk == 0) {
  352.         temp = this._subWord(this._rotWord(temp));
  353.  
  354.         for (let t = 0; t < 4; t++) {
  355.           temp[t] ^= RCON[i / Nk][t];
  356.         }
  357.       } else if (Nk > 6 && i % Nk == 4) {
  358.         temp = this._subWord(temp);
  359.       }
  360.  
  361.       for (let t = 0; t < 4; t++) {
  362.         w[i][t] = w[i-Nk][t] ^ temp[t];
  363.       }
  364.     }
  365.  
  366.     return w;
  367.   },
  368.  
  369.   /**
  370.    * Apply sbox to 4-byte word w.
  371.    * @param aWord the word to transform.
  372.    * @return the transformed word.
  373.    */
  374.   _subWord : function(aWord) {
  375.     this._logger.trace("_subWord");
  376.  
  377.     for (let i = 0; i < 4; i++) {
  378.       aWord[i] = SBOX[aWord[i]];
  379.     }
  380.  
  381.     return aWord;
  382.   },
  383.  
  384.   /**
  385.    * Rotate 4-byte word w left by one byte.
  386.    * @param aWord the word to transform.
  387.    * @return the transformed word.
  388.    */
  389.   _rotWord : function(aWord) {
  390.     this._logger.trace("_rotWord");
  391.  
  392.     aWord[4] = aWord[0];
  393.  
  394.     for (let i = 0; i < 4; i++) {
  395.       aWord[i] = aWord[i + 1];
  396.     }
  397.  
  398.     return aWord;
  399.   },
  400.  
  401.   /**
  402.    * Unescape potentially problematic control characters.
  403.    * @param aString the string to unescape.
  404.    * @return the unescaped string.
  405.    */
  406.   _unescCtrlChars : function(aString) {
  407.     this._logger.trace("_unescCtrlChars");
  408.  
  409.     let unescaped =
  410.       aString.replace(
  411.         RE_UNESCAPE_REPLACE,
  412.         function(c) { return String.fromCharCode(c.slice(1, -1)); });
  413.  
  414.     return unescaped;
  415.   },
  416.  
  417.   /**
  418.    * Generates the MD5 hash of the given string. Taken from
  419.    * http://developer.mozilla.org/en/docs/nsICryptoHash#
  420.    * Computing_the_Hash_of_a_String
  421.    * @param aString the string to hash to MD5.
  422.    * @return the hashed string.
  423.    */
  424.   _md5Hash : function(aString) {
  425.     this._logger.trace("_md5Hash");
  426.  
  427.     let converter =
  428.       Cc["@mozilla.org/intl/scriptableunicodeconverter"].
  429.         createInstance(Ci.nsIScriptableUnicodeConverter);
  430.     let hash =
  431.       Components.classes["@mozilla.org/security/hash;1"].
  432.         createInstance(Components.interfaces.nsICryptoHash);
  433.     let decoder =
  434.       function(aCharCode) { return ("0" + aCharCode.toString(16)).slice(-2); };
  435.     let data;
  436.     let hashedData;
  437.  
  438.     converter.charset = "UTF-8";
  439.     // data is an array of bytes.
  440.     data = converter.convertToByteArray(aString, {});
  441.  
  442.     // perform the hashing operation.
  443.     hash.init(hash.MD5);
  444.     hash.update(data, data.length);
  445.     hashedData = hash.finish(false);
  446.  
  447.     // convert the binary hash data to a hex string.
  448.     return [decoder(hashedData.charCodeAt(i)) for (i in hashedData)].join("");
  449.   }
  450. };
  451.  
  452. /**
  453.  * FireFM.Secret constructor.
  454.  */
  455. (function() {
  456.   this.init();
  457. }).apply(FireFM.Secret);
  458.